﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Errors;
using EffiProz.Core.RowIO;
using EffiProz.Core.Results;
using EffiProz.Core.Persist;
using EffiProz.Core.Indexes;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Lib;

/**
 * Implementation or RowSetNavigator using a table as the data store.
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 1.9.0
 * @since 1.9.0
 */

namespace EffiProz.Core.Navigators
{
    public class RowSetNavigatorData : RowSetNavigator
    {
        protected new Session session;
        public TableBase table;
        public PersistentStore store;
        RowIterator iterator;
        Row currentRow;
        int maxMemoryRowCount;
        bool isClosed;
        int visibleColumnCount;
        bool isAggregate;
        bool isSimpleAggregate;
        Object[] simpleAggregateData;

        //
        bool reindexTable;

        //
        private Index mainIndex;
        private Index fullIndex;
        private Index orderIndex;
        private Index groupIndex;

        public RowSetNavigatorData(Session session, QuerySpecification select)
        {

            this.session = session;
            maxMemoryRowCount = session.getResultMemoryRowCount();
            visibleColumnCount = select.indexLimitVisible;
            table = select.resultTable.duplicate();
            table.store = store = session.sessionData.getNewResultRowStore(table,
                    !select.isAggregated);
            isAggregate = select.isAggregated;
            isSimpleAggregate = select.isAggregated && !select.isGrouped;
            reindexTable = select.isGrouped;
            mainIndex = select.mainIndex;
            fullIndex = select.fullIndex;
            orderIndex = select.orderIndex;
            groupIndex = select.groupIndex;
        }

        public RowSetNavigatorData(Session session,
                                   QueryExpression queryExpression)
        {

            this.session = session;
            maxMemoryRowCount = session.getResultMemoryRowCount();
            table = queryExpression.resultTable.duplicate();
            visibleColumnCount = table.getColumnCount();
            table.store = store = session.sessionData.getNewResultRowStore(table,
                    true);
            mainIndex = queryExpression.mainIndex;
            fullIndex = queryExpression.fullIndex;
        }

        public RowSetNavigatorData(Session session, TableBase table)
        {

            this.session = session;
            maxMemoryRowCount = session.getResultMemoryRowCount();
            this.table = table;
            visibleColumnCount = table.getColumnCount();
            store = session.sessionData.getRowStore(table);
            mainIndex = table.getPrimaryIndex();
            fullIndex = table.getFullIndex();
            this.size = mainIndex.size(store);
        }

        public void sortFull()
        {

            if (reindexTable)
            {
                store.indexRows();
            }

            mainIndex = fullIndex;

            reset();
        }

        public void sortOrder()
        {

            if (orderIndex != null)
            {
                if (reindexTable)
                {
                    store.indexRows();
                }

                mainIndex = orderIndex;

                reset();
            }
        }

        public void sortUnion(SortAndSlice sortAndSlice)
        {

            if (sortAndSlice.index != null)
            {
                mainIndex = sortAndSlice.index;

                reset();
            }
        }

        public void sortGroup()
        {

            mainIndex = groupIndex;

            reset();
        }

        public override void add(Object data)
        {

            try
            {
                Row row = (Row)store.getNewCachedObject(session, data);

                store.indexRow(null, row);

                size++;
            }
            catch (CoreException) { }
        }

        private void addAdjusted(Object[] data, int[] columnMap)
        {

            try
            {
                if (columnMap == null)
                {
                    data = (Object[])ArrayUtil.resizeArrayIfDifferent(data,
                            table.getColumnCount());
                }
                else
                {
                    Object[] newData = new Object[table.getColumnCount()];

                    ArrayUtil.projectRow(data, columnMap, newData);

                    data = newData;
                }

                Row row = (Row)store.getNewCachedObject(session, data);

                store.indexRow(null, row);

                size++;
            }
            catch (CoreException) { }
        }

        public override void clear()
        {

            table.clearAllData(store);

            size = 0;

            reset();
        }

        public override Object[] getCurrent()
        {
            return currentRow.getData();
        }

        public override Row getCurrentRow()
        {
            return currentRow;
        }

        public override bool next()
        {

            bool result = base.next();

            currentRow = iterator.getNextRow();

            return result;
        }

        public override void remove()
        {

            if (currentRow != null)
            {
                iterator.remove();

                currentRow = null;

                currentPos--;
                size--;
            }
        }

        public override void reset()
        {

            base.reset();

            iterator = mainIndex.firstRow(store); 
        }

        public override void close()
        {

            if (isClosed)
            {
                return;
            }

            iterator.release();
            store.release();

            isClosed = true;
        }

        public override bool isMemory()
        {
            return store.isMemory();
        }

        public override void read(RowInputInterface _in,
                         ResultMetaData meta) { }

        public override void write(RowOutputInterface _out,
                          ResultMetaData meta)
        {

            reset();
            _out.writeLong(id);
            _out.writeInt(size);
            _out.writeInt(0);    // offset
            _out.writeInt(size);

            while (hasNext())
            {
                Object[] data = (Object[])getNext();

                _out.writeData(meta.getExtendedColumnCount(), meta.columnTypes,
                              data, null, null);
            }

            reset();
        }

        public void copy(RowSetNavigatorData other, int[] rightColumnIndexes)
        {

            while (other.hasNext())
            {
                other.getNext();

                Object[] currentData = other.currentRow.getData();

                addAdjusted(currentData, rightColumnIndexes);
            }

            other.close();
        }

        public void union(RowSetNavigatorData other, int[] rightColumnIndexes)
        {

            Object[] currentData;

            removeDuplicates();
            reset();

            while (other.hasNext())
            {
                other.getNext();

                currentData = other.currentRow.getData();

                RowIterator it = fullIndex.findFirstRow(session, store,
                    currentData, rightColumnIndexes);

                if (!it.hasNext())
                {
                    addAdjusted(currentData, rightColumnIndexes);
                }
            }

            other.close();
        }

        public void unionAll(RowSetNavigatorData other, int[] rightColumnIndexes)
        {

            other.reset();

            while (other.hasNext())
            {
                other.getNext();

                Object[] currentData = other.currentRow.getData();

                addAdjusted(currentData, rightColumnIndexes);
            }

            other.close();
        }

        public void intersect(RowSetNavigatorData other)
        {

            removeDuplicates();
            reset();
            other.sortFull();

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();
                RowIterator it = other.fullIndex.findFirstRow(session,
                    other.store, currentData);

                if (!it.hasNext())
                {
                    remove();
                }
            }

            other.close();
        }

        public void intersectAll(RowSetNavigatorData other)
        {

            Object[] compareData = null;
            RowIterator it;
            Row otherRow = null;
            Object[] otherData = null;

            sortFull();
            reset();
            other.sortFull();

            it = other.fullIndex.emptyIterator();

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();
                bool newGroup =
                    compareData == null
                    || fullIndex.compareRowNonUnique(
                        session, currentData, compareData,
                        fullIndex.getColumnCount()) != 0;

                if (newGroup)
                {
                    compareData = currentData;
                    it = other.fullIndex.findFirstRow(session, other.store,
                                                      currentData);
                }

                otherRow = it.getNextRow();
                otherData = otherRow == null ? null
                                             : otherRow.getData();

                if (otherData != null
                        && fullIndex.compareRowNonUnique(
                            session, currentData, otherData,
                            fullIndex.getColumnCount()) == 0)
                {
                    continue;
                }

                remove();
            }

            other.close();
        }

        public void except(RowSetNavigatorData other)
        {

            removeDuplicates();
            reset();
            other.sortFull();

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();
                RowIterator it = other.fullIndex.findFirstRow(session,
                    other.store, currentData);

                if (it.hasNext())
                {
                    remove();
                }
            }

            other.close();
        }

        public void exceptAll(RowSetNavigatorData other)
        {

            Object[] compareData = null;
            RowIterator it;
            Row otherRow = null;
            Object[] otherData = null;

            sortFull();
            reset();
            other.sortFull();

            it = other.fullIndex.emptyIterator();

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();
                bool newGroup =
                    compareData == null
                    || fullIndex.compareRowNonUnique(
                        session, currentData, compareData,
                        fullIndex.getColumnCount()) != 0;

                if (newGroup)
                {
                    compareData = currentData;
                    it = other.fullIndex.findFirstRow(session, other.store,
                                                      currentData);
                }

                otherRow = it.getNextRow();
                otherData = otherRow == null ? null
                                             : otherRow.getData();

                if (otherData != null
                        && fullIndex.compareRowNonUnique(
                            session, currentData, otherData,
                            fullIndex.getColumnCount()) == 0)
                {
                    remove();
                }
            }

            other.close();
        }

        public bool hasUniqueNotNullRows()
        {

            sortFull();
            reset();

            Object[] lastRowData = null;

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();

                if (hasNull(currentData))
                {
                    continue;
                }

                if (lastRowData != null && Equals(lastRowData, currentData))
                {
                    return false;
                }
                else
                {
                    lastRowData = currentData;
                }
            }

            return true;
        }

        public void removeDuplicates()
        {

            sortFull();
            reset();

            Object[] lastRowData = null;

            while (hasNext())
            {
                getNext();

                Object[] currentData = currentRow.getData();

                if (lastRowData != null && Equals(lastRowData, currentData))
                {
                    remove();
                }
                else
                {
                    lastRowData = currentData;
                }
            }
        }

        public void trim(int limitstart, int limitcount)
        {

            if (size == 0)
            {
                return;
            }

            if (limitstart >= size)
            {
                clear();

                return;
            }

            if (limitstart != 0)
            {
                reset();

                for (int i = 0; i < limitstart; i++)
                {
                    next();
                    remove();
                }
            }

            if (limitcount == 0 || limitcount >= size)
            {
                return;
            }

            reset();

            for (int i = 0; i < limitcount; i++)
            {
                next();
            }

            while (hasNext())
            {
                next();
                remove();
            }
        }

        private bool hasNull(Object[] data)
        {

            for (int i = 0; i < visibleColumnCount; i++)
            {
                if (data[i] == null)
                {
                    return true;
                }
            }

            return false;
        }

        public bool Equals(Object[] data1, Object[] data2)
        {

            SqlType[] types = table.getColumnTypes();

            for (int i = 0; i < visibleColumnCount; i++)
            {
                if (types[i].compare(session, (object)data1[i], (object)data2[i]) != 0)
                {
                    return false;
                }
            }

            return true;
        }

        /**
         * Special case for isSimpleAggregate cannot use index lookup.
         */
        public Object[] getGroupData(Object[] data)
        {

            if (isSimpleAggregate)
            {
                if (simpleAggregateData == null)
                {
                    simpleAggregateData = data;

                    return null;
                }

                return simpleAggregateData;
            }

            RowIterator it = groupIndex.findFirstRow(session, store, data);

            if (it.hasNext())
            {
                Row row = it.getNextRow();

                if (isAggregate)
                {
                    row.setChanged();
                }

                return row.getData();
            }

            return null;
        }
    }
}
